package com.lijz.aspect;

import com.lijz.annotation.UserAccess;
import com.lijz.constants.Constants;
import com.lijz.log.ILogService;
import com.lijz.log.LogCallable;
import com.lijz.log.SysLog;
import com.lijz.utils.HttpUtil;
import com.lijz.utils.IdGenerator;
import com.lijz.utils.RedisUtil;
import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 因为Spring基于动态代理， 所以Spring只支持方法连接点。 这与
 * 一些其他的AOP框架是不同的， 例如AspectJ和JBoss， 除了方法切
 * 点， 它们还提供了字段和构造器接入点。
 * <p>
 * AspectJ指示器 描 述
 * arg() 限制连接点匹配参数为指定类型的执行方法    注：用于传递参数给切面方法
 *
 * @args() 限制连接点匹配参数由指定注解标注的执行方法
 * execution() 用于匹配是连接点的执行方法
 * execution(public * com.dbgo..*.app.*.*(..)) 表示任意返回值类型 com.dbgo包下任意app子包下的任意方法，任意参数
 * this() 限制连接点匹配AOP代理的bean引用为指定类型的类
 * target 限制连接点匹配目标对象为指定类型的类
 * @target() 限制连接点匹配特定的执行对象， 这些对象对应的类要具有指定类型的注解
 * within() 限制连接点匹配指定的类型
 * @within() 限制连接点匹配指定注解所标注的类型（当使用Spring AOP时， 方法定义在由指定的注解所标注的类里）
 * @annotation 限定匹配带有指定注解的连接点
 * <p>
 * 注意只有execution指示器是实际执行匹配的， 而其他的指示器都是用来限制匹配的。
 * <p>
 * 通过切面还可以在不修改源码的情况下为对象增加新的功能、方法  使用@DeclareParents注解
 */
@Aspect
@Component
public class ActionAspect {

	private Logger logger = LoggerFactory.getLogger(ActionAspect.class);

	private ThreadLocal<Long> startTime = new ThreadLocal<>();
	private ThreadLocal<SysLog> logThreadLocal = new ThreadLocal<>();
	private ThreadLocal<String> logIdAndcollectionName = new ThreadLocal<>();

	@Autowired
	private ThreadPoolExecutor threadPoolExecutor;

	@Autowired
	private ILogService logService;

	@Autowired
	private RedisUtil redisUtil;

	/*
	 * 第一种方法
	 */
	//定义切点
	@Pointcut("@annotation(com.lijz.annotation.OperationLog)")
	public void logAspect() {
	}

	@Pointcut("@annotation(com.lijz.annotation.UserAccess)")
	public void controllerAspect() {
	}

	@Around("controllerAspect()")
	public Object doControllerAround(ProceedingJoinPoint joinPoint) throws Throwable {
		long startLog = System.currentTimeMillis();
		logger.info("---------------------请求日志before--------------------");
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = requestAttributes.getRequest();
		StringBuilder inputStr = new StringBuilder();

		Method method = signature.getMethod();
		UserAccess annotation = method.getAnnotation(UserAccess.class);
		//		Object target = joinpoint.getTarget();
//		UserAccess annotation1 = target.getClass().getAnnotation(UserAccess.class);

		String description = annotation.description();
		String url = request.getRequestURL().toString();
		String IP = request.getRemoteAddr();
		String classMethod = signature.getDeclaringTypeName() + "." + signature.getName();
		String requestURI = request.getRequestURI();
		String requestMethod = request.getMethod();

		SysLog log = new SysLog();
		logThreadLocal.set(log);
		log.setOperateDate(new Date());
		log.setException("无异常");
		log.setTitle(description);
		log.setLogId(IdGenerator.getId());
		log.setMethod(requestMethod);
		log.setOperateDate(new Date());
		log.setRequestUri(requestURI);
		log.setType("info");
		log.setRemoteAddr(IP);

		logger.info("URL : " + url);
		logger.info("HTTP_METHOD : " + method);
		logger.info("IP : " + IP);
		logger.info("CLASS_METHOD : " + classMethod);

//		logger.info("ARGS : " + Arrays.toString(joinpoint.getArgs()));
		startTime.set(System.currentTimeMillis());
		inputStr.append("Http Headers:\r\n");
		Enumeration<String> httpHeaderNames = request.getHeaderNames();
		while (httpHeaderNames.hasMoreElements()) {
			String headerName = httpHeaderNames.nextElement();
			Enumeration<String> headerValues = request.getHeaders(headerName);
			while (headerValues.hasMoreElements()) {
				inputStr.append(headerName).append(":").append(headerValues.nextElement())
						.append("\r\n");
			}
		}
		inputStr.append("\r\nUrl Params:\r\n");
		Enumeration<?> paramNames = request.getParameterNames();
		while (paramNames.hasMoreElements()) {
			String paramName = (String) paramNames.nextElement();
			String paramValue = request.getParameter(paramName);
			inputStr.append(paramName).append("=").append(paramValue).append("&");
		}
		inputStr.append("\r\nRequest Body:\r\n");
		Map<String, Object> params = getRequestParams(joinPoint);
//		inputStr.append(requestParams);
		inputStr.append(params.toString());
		//获取用户凭证
		String token = request.getParameter(Constants.TOKEN);
		long startRedis = System.currentTimeMillis();
		Object hgetUserId = redisUtil.hget(Constants.TOKEN_ + token, Constants.USERID);
		logger.debug("redis 执行时间：" + (System.currentTimeMillis() - startRedis));
		String userid = "";
		if (hgetUserId != null) {
			userid = hgetUserId.toString();
		}

		log.setParams(inputStr.toString());
		log.setUserId(userid);
		log.setToken(token);

		// 打印JVM信息。
		long beginTime = startTime.get();//得到线程绑定的局部变量（开始时间）  
		long endTime = System.currentTimeMillis(); //2、结束时间  
		if (logger.isDebugEnabled()) {
			logger.debug("计时结束：{}  URI: {}  耗时： {}   最大内存: {}m  已分配内存: {}m  已分配内存中的剩余空间: {}m  最大可用内存: {}m",
					new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(endTime),
					request.getRequestURI(),
					(endTime - beginTime),
					Runtime.getRuntime().maxMemory() / 1024 / 1024,
					Runtime.getRuntime().totalMemory() / 1024 / 1024,
					Runtime.getRuntime().freeMemory() / 1024 / 1024,
					(Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory() + Runtime.getRuntime().freeMemory()) / 1024 / 1024);
		}

		logger.info("输入参数:\r\n" + inputStr);
		logger.info("---------------------请求日志end--------------------");
		logger.debug("请求日志之前执行时间：" + (System.currentTimeMillis() - startLog));

		// 保存日志
		FutureTask<Object> saveTask = new FutureTask<>(new LogCallable(userid, token, log, System.currentTimeMillis(), logService));
		threadPoolExecutor.execute(saveTask);
		String ret = (String) saveTask.get();
		logIdAndcollectionName.set(ret);
		long startApi = System.currentTimeMillis();
		Object result = joinPoint.proceed();
		logger.debug("接口执行时间：" + (System.currentTimeMillis() - startApi));
		logger.info("----------------------执行结束---------------------");
		long timeout = System.currentTimeMillis() - startTime.get();
		logger.info("执行时间：" + timeout);
		log.setTimeout(timeout);
		if (result != null) {
			String temp = result.toString();
			log.setResultParams(temp);
			logger.info("执行结果：" + temp);
		}
		logger.info("-------------------执行结束   end-------------------");
		//更新日志
		FutureTask<Object> updateTask = new FutureTask<>(new LogCallable(log, logIdAndcollectionName.get(), logService));
		threadPoolExecutor.execute(updateTask);

		return result;
	}

	private Map<String, Object> getRequestParams(ProceedingJoinPoint joinPoint) {
		//入参  value
		Object[] args = joinPoint.getArgs();
		//入参名称
		String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
		Map<String, Object> params = new HashMap<>();
		//获取所有参数对象
		for (int i = 0; i < args.length; i++) {
			if (null != args[i]) {
				if (args[i] instanceof BindingResult) {
					params.put(paramNames[i], "bindingResult");
				} else {
					params.put(paramNames[i], args[i]);
				}
			} else {
				params.put(paramNames[i], "无");
			}
		}
		return params;
	}

	/*或者这样写：不定义切点*/
//	@Around("@annotation(userAccess)")
//	public void doControllerAround(ProceedingJoinPoint point, UserAccess userAccess) throws Throwable {
//
//	}

	/**
	 *      *  异常通知 记录操作报错日志
	 *      * @param joinPoint
	 *      * @param e
	 *     
	 */
	@AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
		logger.info("---------------进入日志切面异常通知!!--------------");
		logger.info("异常信息:\r\n" + e.toString() + "\r\n" + e.getStackTrace()[0].toString());
		logger.info("-------------------异常  end-----------------------");
		SysLog log = logThreadLocal.get();
		if (log != null) {    //更新日志
			log.setType("error");
			log.setException(e.toString() + ";" + e.getStackTrace()[0].toString());
			FutureTask<Object> updateTask = new FutureTask<>(new LogCallable(log, logIdAndcollectionName.get(), logService));
			threadPoolExecutor.execute(updateTask);
		} else {        //保存日志
			SysLog sysLog = new SysLog();
			sysLog.setLogId(IdGenerator.getId());
			log.setType("error");
			log.setException(e.toString());
			FutureTask<Object> saveTask = new FutureTask<>(new LogCallable("", "", sysLog, System.currentTimeMillis(), logService));
			threadPoolExecutor.execute(saveTask);
		}


	}

}
